﻿#region

using System;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Media;

#endregion

namespace Radovici.Ctrls
{
    public class HtmlBlock : Control, IDisposable
    {
        #region Private fields

        private readonly object _lock = new object();
        private HtmlElement _body;
        private HtmlElement _container;

        #endregion

        public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.Register("IsVisible",
                                                                                                  typeof (bool),
                                                                                                  typeof (HtmlBlock),
                                                                                                  new PropertyMetadata(
                                                                                                      true,
                                                                                                      OnIsVisibleChanged));

        public HtmlBlock()
        {
            Overflow = true;
            LayoutUpdated += onLayoutUpdated;
        }

        #region Html dependency property

        public static readonly DependencyProperty HtmlProperty =
            DependencyProperty.Register("Html", typeof (string), typeof (HtmlBlock),
                                        new PropertyMetadata(string.Empty, onHtmlChanged));

        public string Html
        {
            get { return (string) GetValue(HtmlProperty); }
            set { SetValue(HtmlProperty, value); }
        }

        private static void onHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var block = d as HtmlBlock;
            if (block != null)
            {
                block.refreshHtml();
            }
        }

        #endregion

        public bool IsVisible
        {
            get { return (bool) GetValue(IsVisibleProperty); }
            set { SetValue(IsVisibleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsVisible.  This enables animation, styling, binding, etc...

        public bool Overflow { get; set; }

        #region Private event handlers

        private void onLayoutUpdated(object sender, EventArgs e)
        {
            refreshView();
        }

        #endregion

        #region Private methods

        private bool checkViewState()
        {
            if (_container == null)
            {
                lock (_lock)
                {
                    if (_container == null)
                    {
                        loadView();
                    }
                }
            }
            return _container != null;
        }

        private void loadView()
        {
            HtmlDocument doc = HtmlPage.Document;
            _body = doc.GetElementsByTagName("body")[0] as HtmlElement;
            if (_body != null && _container == null)
            {
                _container = doc.CreateElement("div");
                _container.SetStyleAttribute("position", "absolute");
                _container.SetStyleAttribute("overflow", Overflow ? "auto" : "hidden");
//                _container.SetProperty("innerHTML", @"Sample text showing how <b>HtmlTextBlock</b> makes it <i>*easy*</i> to display <strong>rich</strong> text in a Silverlight application by using a <em>simple</em> subset of <u>well-formed</u> HTML markup and a custom control.
//                <p>Experiment by typing <i>your own</i> markup into the text box at the bottom to see how it gets rendered by Silverlight's <b>TextBlock</b>, by the custom <b>HtmlTextBlock</b>, and by the browser.</p>
//                <p>Note: <b>HtmlTextBlock</b> displays invalid <a href=""http://en.wikipedia.org/wiki/Xhtml"">XHTML</a> as-is.</p>");
                _body.AppendChild(_container);
                _container.AttachHtmlElement();
                refreshView();
            }
        }

        private void refreshView()
        {
            if (checkViewState())
            {
                if (!IsVisible || RenderSize == new Size(0, 0))
                {
                    _container.SetStyleAttribute("width", "0px");
                    _container.SetStyleAttribute("height", "0px");
                    _container.SetAttribute("width", "0px");
                    _container.SetAttribute("height", "0px");
                }
                else
                {
                    UIElement root = Application.Current.RootVisual;
                    GeneralTransform gt = TransformToVisual(root);
                    Point pos = gt.Transform(new Point(0, 0));
                    _container.SetStyleAttribute("left", pos.X + "px");
                    _container.SetStyleAttribute("top", pos.Y + "px");
                    _container.SetStyleAttribute("width", ActualWidth + "px");
                    _container.SetStyleAttribute("height", ActualHeight + "px");
                    _container.SetAttribute("width", ActualWidth + "px");
                    _container.SetAttribute("height", ActualHeight + "px");
                    //Debug.WriteLine(string.Format("HtmlBlock:RefreshView Rectangle = ({0},{1},{2},{3},{4},{5})", pos.X, pos.Y, this.ActualWidth, this.ActualHeight, this.DesiredSize.Width, this.DesiredSize.Height));
                }
            }
        }

        private void refreshHtml()
        {
            if (checkViewState())
            {
                string font = string.Empty;
                if (FontFamily != null)
                {
                    font = FontFamily.ToString();
                    int hash = font.IndexOf("#"); //NOTE: Custom fonts.
                    if (hash >= 0) font = font.Substring(hash + 1);
                }
                //string html = string.Format("<div style=\"font-family:{0};font-size:{1};\">{2}</div>", font, this.FontSize.ToString(), this.Html);
                _container.SetStyleAttribute("font-family", font);
                _container.SetStyleAttribute("font-size", FontSize.ToString());
                _container.SetProperty("innerHTML", Html);
            }
        }

        #endregion

        #region IDisposable

        private bool disposed;

        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to take this object off the finalization queue
            // and prevent finalization code for this object from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed.
        // If disposing equals false, the method has been called by the runtime from inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed.
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!disposed)
            {
                // If disposing equals true, dispose all managed and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    if (_body != null)
                    {
                        if (_container != null)
                        {
                            _body.RemoveChild(_container);
                            _container = null;
                        }
                    }
                }
                // Call the appropriate methods to clean up unmanaged resources here.
                // If disposing is false, only the following code is executed.
                // Note disposing has been done.
                disposed = true;
            }
        }

        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~HtmlBlock()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of readability and maintainability.
            Dispose(false);
        }

        #endregion

        private static void OnIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var block = d as HtmlBlock;
            if (block != null)
            {
                block.refreshView();
            }
        }
    }
}